
using System.Diagnostics;
using System.Globalization;

namespace SharpTalk.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Remoting.Messaging;
    using System.ServiceModel;
    using Core;
    using Core.Delegates;
    using Core.Enums;
    using Core.Exceptions;
    using Core.Interfaces;
    using Core.Resources;
    using Core.Services;
    using Core.Services.Contracts.Server;
    using Core.Services.Requests;
    using Core.Services.Responses;
    using Core.Extensions;
    using System.Threading.Tasks;

    [ServiceBehavior(Namespace = "http://SharpTalk/", ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public sealed class ChatRoomServices :
        RequestServiceBase<
            ChatRoomCreateRequest, ChatRoomCreateResponse,
            ChatRoomFindRequest, ChatRoomFindResponse,
            ChatRoomUpdateRequest, ChatRoomUpdateResponse,
            ChatRoomDeleteRequest, ChatRoomDeleteResponse>, 
        IChatRoomServices
    {
        private readonly IChatRoomManager _manager = null;
        private readonly IUserManager _userManager;
        
        private ChatRoomServices() : base()
        {
        }

        public ChatRoomServices(IUserManager userManager, IChatRoomManager chatRoomManager)
        {
            if (userManager == null) throw new ArgumentNullException("userManager");
            if (chatRoomManager == null) throw new ArgumentNullException("chatRoomManager");

            _userManager = userManager;
            _manager = chatRoomManager;
        }

        public IChatRoomManager Manager { get { return _manager; } }

        /// <summary>
        /// Checks if the create request can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public override bool CanCreate(ChatRoomCreateRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            CheckRequest(request, () => request != null &&
                                        request.Owner != Guid.Empty &&
                                        !string.IsNullOrEmpty(request.Content.Name) &&
                                        request.Content.Type != ChatRoomTypeEnum.NotDefined);
            return base.CanCreate(request);
        }
        /// <summary>
        /// Checks if the update request can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public override bool CanUpdate(ChatRoomUpdateRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            CheckRequest(request);
            return base.CanUpdate(request);
        }
        /// <summary>
        /// Checks if the find request can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public override bool CanFind(ChatRoomFindRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            CheckRequest(request);
            return base.CanFind(request);
        }
        /// <summary>
        /// Checks if the delete request can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public override bool CanDelete(ChatRoomDeleteRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            CheckRequest(request);
            return base.CanDelete(request);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public override ChatRoomCreateResponse Create(ChatRoomCreateRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                var room = _manager.Create(request.Content.Name, request.Content.Type, request.Content.PasswordHash, request.Content.Motd, request.Owner);
                return room !=null 
                    ? new ChatRoomCreateResponse() { Content = room.Guid } 
                    : new ChatRoomCreateResponse() { Error = new ErrorDetails(Messages.chatroom_name_already_exists) };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(),exp);
                throw new ChatRoomException(exp.Message);
            }
        }

        public override ChatRoomFindResponse Find(ChatRoomFindRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                var response = new ChatRoomFindResponse();

                if( String.IsNullOrEmpty(request.Content.Name) 
                    && request.Content.Type.HasFlag(ChatRoomTypeEnum.Public))
                {
                    // searching all public chatrooms (protected or not)

                    var result = _manager
                        .Find(request.Content.Type)
                        .Select(chatRoom => new ChatRoomDetails(chatRoom))
                        .ToList();

                    response.Content = result;
                }
                else if(!String.IsNullOrEmpty(request.Content.Name) 
                    && request.Content.Type == ChatRoomTypeEnum.NotDefined)
                {
                    // Searching a chatroom by its name ( All allowed types included )

                    var result = _manager.Find(request.Content.Name);

                    response.Content = result == null
                        ? new List<ChatRoomDetails>()
                        : new List<ChatRoomDetails>(){ new ChatRoomDetails(result) };
                }
                else if(!String.IsNullOrEmpty(request.Content.Name) 
                    && request.Content.Type != ChatRoomTypeEnum.NotDefined )
                {
                    // Searching a chatroom by its name and specifc type ( All allowed types included )

                    var result = _manager.Find(request.Content.Name, request.Content.Type);
                    response.Content = result == null
                        ? new List<ChatRoomDetails>()
                        : new List<ChatRoomDetails>() { new ChatRoomDetails(result)  };
                }
                else
                {
                    // Rest not allowed
                    response.Content = new List<ChatRoomDetails>();
                }
                
                return response;
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                throw new ChatRoomException(exp.Message) ;
            }
        }

        public override ChatRoomUpdateResponse Update(ChatRoomUpdateRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                var chatroom = _manager.Find(request.Content.Guid);
                
                if(chatroom == null)
                    throw new ChatRoomException(Messages.chatroom_not_found);
                if (request.Owner == Guid.Empty || chatroom.Owner != request.Owner) 
                    throw new ChatRoomException(Messages.owner_invalid);
                
                chatroom.Type = request.Content.Type;
                chatroom.Image = request.Content.Image;
                chatroom.Motd = request.Content.Motd;
                if (request.Content.DefinePassword)
                    chatroom.PasswordHash = request.Content.PasswordHash;

                var room = _manager.Update(chatroom, request.Content.DefinePassword);
                
                return new ChatRoomUpdateResponse()
                           {
                               Content = new ChatRoomDetails(room){ Owner = request.Owner }
                           };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                throw new ChatRoomException(exp.Message);
            }
        }

        public override ChatRoomDeleteResponse Delete(ChatRoomDeleteRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                var chatroom = _manager.Find(request.Content.Guid);

                if (chatroom == null)
                    throw new ChatRoomException(Messages.chatroom_not_found);
                if (request.Owner == Guid.Empty|| chatroom.Owner != request.Owner)
                    throw new ChatRoomException(Messages.owner_invalid);

                return new ChatRoomDeleteResponse() { Content = _manager.Delete(chatroom) };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                throw new ChatRoomException(exp.Message);
            }
        }

        private ChatRoomJoinResponse JoinChatRoom(ChatRoomJoinRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());

            try
            {
                CheckRequest(request);
                var chatroom = _manager.Join(request.Content.Guid, request.Owner, request.Content.PasswordHash);
#if DEBUG

                TraceDebug("User {0:N} {1}",
                    request.Owner,
                    chatroom == null
                        ? "Failed to join chatroom."
                        : string.Format("Joined Chatroom {0} ({1:N}).", chatroom.Name, chatroom.Guid));
#endif

                if (chatroom != null)
                {
                    TraceDebug("Notifying user {0:N} joined chatroom {1:N}.", request.Owner, chatroom.Guid);
                    Task.Factory.StartNew(() => NotifyChatRoom(request.Content.Guid, request.Owner, MessageTypeEnum.UserJoin));
                }
                
                return new ChatRoomJoinResponse()
                           {
                               Content = chatroom,
                               Error = chatroom == null ? new ErrorDetails() : null
                           };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp, true);
                return new ChatRoomJoinResponse() { Error = new ErrorDetails(exp) };
            }
        }
        public IAsyncResult BeginJoinChatRoom(ChatRoomJoinRequest request, AsyncCallback callback, object state)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<ChatRoomJoinRequest, ChatRoomJoinResponse>(JoinChatRoom);
            return del.BeginInvoke(request, callback, state);
        }
        public ChatRoomJoinResponse EndJoinChatRoom(IAsyncResult result)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<ChatRoomJoinRequest, ChatRoomJoinResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }

        private ChatRoomLeaveResponse LeaveChatRoom(ChatRoomLeaveRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                CheckRequest(request);
                var success = _manager.Leave(request.Content.Guid, request.Owner);
                if(success)
                {
                    TraceDebug("Notifying user {0:N} left chatroom {1:N}.", request.Owner, request.Content.Guid);
                    Task.Factory.StartNew(() => NotifyChatRoom(request.Content.Guid, request.Owner, MessageTypeEnum.UserLeft));
                }

                return new ChatRoomLeaveResponse(){ Content = success };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                return new ChatRoomLeaveResponse() { Error = new ErrorDetails(exp) };
            }
        }
        public IAsyncResult BeginLeaveChatRoom(ChatRoomLeaveRequest request, AsyncCallback callback, object state)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<ChatRoomLeaveRequest, ChatRoomLeaveResponse>(LeaveChatRoom);
            return del.BeginInvoke(request, callback, state);
        }
        public ChatRoomLeaveResponse EndLeaveChatRoom(IAsyncResult result)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<ChatRoomLeaveRequest, ChatRoomLeaveResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }

        private ChatRoomUserListResponse GetUserList(ChatRoomUserListRequest request)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            try
            {
                CheckRequest(request);

                var chatroom = _manager.Find(request.Content.Guid);
                return new ChatRoomUserListResponse()
                {
                    Content = chatroom == null
                         ? new List<Guid>()
                         : chatroom.Users ?? new List<Guid>()
                };
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                return new ChatRoomUserListResponse() { Error = new ErrorDetails(exp) };
            }
        }
        public IAsyncResult BeginGetUserList(ChatRoomUserListRequest request, AsyncCallback callback, object state)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<ChatRoomUserListRequest, ChatRoomUserListResponse>(GetUserList);
            return del.BeginInvoke(request, callback, state);
        }
        public ChatRoomUserListResponse EndGetUserList(IAsyncResult result)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<ChatRoomUserListRequest, ChatRoomUserListResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }

        private void NotifyChatRoom(Guid chatroom, Guid userGuid, MessageTypeEnum messageType)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            var room = _manager.Find(chatroom);
            var user = _userManager.Find(userGuid);

            if (room != null && user != null)
                MessageServices.Instance.SendMessage(new MessageSendRequest()
                {
                    Owner = UserService.GetSystemUser().Guid,
                    Content = new MessageSendRequestDetails()
                    {
                        Type = messageType,
                        ChatRoom = room.Guid,
                        Message = new UserNotificationMessage()
                        {
                            Owner = UserService.GetSystemUser().Guid,
                            Content = user
                        }
                    }
                });
        }
    }
}